package Watcher;

import java.io.IOException;
import java.nio.file.FileSystems;
//import java.nio.file.Path;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.HashMap;
import Client.ClientDetails;
import Client.Manager;
import Messages.FileChangeMsg;
import Messages.Msg;
import Messages.MsgType;
import RMI.ManagerAppGUI;

import static java.nio.file.StandardWatchEventKind.*;
/**
 * Watch service - this class listen to folders and update other user that things changed
 * @author Guy & Liel
 *
 */
public class watchService  implements Runnable
{



	private static watchService wsInstance=new watchService();
	volatile boolean play;
	volatile boolean stop;
	WatchService watcher;
	ArrayList<Path> dir;
	WatchKey key;
	private HashMap<WatchKey,String> keys;
	ClientDetails clientDetails;
	Manager manager = Manager.getInstance();

	private watchService(ClientDetails cd)
	{
		super();
		stop = false;
		this.play=false;
		this.clientDetails = cd;
		try {
			this.watcher = FileSystems.getDefault().newWatchService();
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.dir = new ArrayList<Path>();
	}

	private watchService() 
	{
		super();
		keys = new HashMap<WatchKey, String>();
		this.play=false;
		try {
			this.watcher = FileSystems.getDefault().newWatchService();
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.dir = new ArrayList<Path>();
	}
	/**
	 * get status of watch service
	 * @return boolean
	 */
	public boolean isPlay() {
		return play;
	}
	/**
	 * set play - start run service
	 * @param play
	 */
	public void setPlay(boolean play) {
		System.out.println("change play in watcher to "+play);
		this.play = play;
		//notify the thread that the status had change
		System.out.println("before sync");
		synchronized (this) {
			System.out.println("before notify");
			notify();
		}
		System.out.println("after all");
		//		notify();
	}
	/**
	 * stop watch
	 */
	public void stopWatch(){
		stop = true;
		synchronized (this) {
			notify();
		}
	}
	/**
	 * get instance of the watch service class
	 * @return watchService
	 */
	public static watchService getWsInstance() {
		return wsInstance;
	}
	/**
	 * get client details
	 * @return ClientDetails
	 */
	public ClientDetails getClientDetails() {
		return clientDetails;
	}
	/**
	 * set client details
	 * @param clientDetails
	 */
	public void setClientDetails(ClientDetails clientDetails) {
		this.clientDetails = clientDetails;
	}

	/**
	 * add folder to list of folder to listen them
	 * @param strDirectory
	 */
	public void addFolderToListen(String strDirectory)
	{
		System.out.println("adding folder "+strDirectory);
		dir.add(FileSystems.getDefault().getPath(strDirectory));
		try 
		{
			key = FileSystems.getDefault().getPath(strDirectory).register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
			keys.put(key,strDirectory);
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}

	}
	/**
	 * Thread::listen to folders - using java 7 
	 */
	public void run() 
	{
		play = true;
		String fullPath = null;
		MsgType type = null;
		String fileCheckSum=null;

		System.out.println("Thread of Watch Services");
		try 
		{
			this.watcher = FileSystems.getDefault().newWatchService();
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		};
		for(Path p:dir)
		{
			try {
				key =p.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
			} catch (IOException e) {
				e.printStackTrace();
			}

		}
		while (!stop) {
			while (play) {
				try {
					key = watcher.take();
					System.out.println("after take, play is "+play);
					if (!play)
					{
						//reset the key, and break;
						key.reset();	
						break;

					}				
				} catch (InterruptedException x)
				{	
					System.err.println("error exeption");
					return;
				}
				for (WatchEvent<?> event : key.pollEvents()) {
					fileCheckSum = null;
					WatchEvent.Kind<?> kind = event.kind();

					//This key is registered only for ENTRY_CREATE events,
					//but an OVERFLOW event can occur regardless if events are
					//lost or discarded.
					if (kind == OVERFLOW) {
						continue;
					}
					//The filename is the context of the event.
					@SuppressWarnings("unchecked")
					WatchEvent<Path> ev = (WatchEvent<Path>) event;
					Path filename = ev.context();
					kind = ev.kind();

					fullPath = keys.get(key) + "\\" + filename.toString();
					System.out.println("the file is " + fullPath);
					int lastIndex = fullPath.lastIndexOf("\\");
					String fileName = fullPath.substring(lastIndex + 1,
							fullPath.length());
					String copyOfPath = fullPath.substring(0, lastIndex);
					lastIndex = copyOfPath.lastIndexOf("\\");
					String folderName = copyOfPath.substring(lastIndex + 1,
							copyOfPath.length());
					System.out.println(folderName);
					System.out.println(fileName + "  " + folderName);
					//if received create event
					if (kind == ENTRY_CREATE) {

						ManagerAppGUI.getManagerAppGUIinstance().addTagToFile(
								fullPath, fileName);
						type = MsgType.CREATE;
					}
					//if received delete event
					if (kind == ENTRY_DELETE) {

						ManagerAppGUI.getManagerAppGUIinstance()
						.removeTagFromFile(fullPath);
						type = MsgType.DELETE;
						fileCheckSum = "test";
					}
					//if received modify event
					if (kind == ENTRY_MODIFY) {
						fullPath = dir.get(0).toString() + "\\"
						+ filename.toString();
						type = MsgType.MODIFY;
					}
					Msg msg;
					msg = new FileChangeMsg(type, fileCheckSum, fileName,
							folderName, fullPath);
					manager.addMsg(msg);
				}
				if (!play)
					continue;
				boolean valid = key.reset();
				if (!valid) {
					break;
				}
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}//end while(play)
			//play probably changed. wait until it will change again
			System.out.println("in while true, going to wait");
			synchronized (this) {
				try {
					System.out.println("trying wait");
					this.wait();
					System.out.println("after wait");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("end of first while, after wait, status is = "+play);
		}//End while(true)

	}
}


